001 /* 002 * Copyright 2005 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.tools.impl; 020 021 import java.io.File; 022 import java.io.IOException; 023 import java.net.URL; 024 import java.net.URI; 025 import java.util.Date; 026 027 import net.dpml.lang.Plugin; 028 import net.dpml.lang.Part; 029 030 import net.dpml.library.info.Scope; 031 import net.dpml.library.Library; 032 import net.dpml.library.Resource; 033 import net.dpml.library.Type; 034 import net.dpml.library.Filter; 035 import net.dpml.library.impl.DefaultLibrary; 036 037 import net.dpml.tools.info.ListenerDirective; 038 039 import net.dpml.tools.Context; 040 041 import net.dpml.transit.Artifact; 042 import net.dpml.transit.Transit; 043 import net.dpml.transit.link.LinkManager; 044 import net.dpml.transit.Layout; 045 import net.dpml.transit.ClassicLayout; 046 047 import net.dpml.util.Logger; 048 import net.dpml.util.DefaultLogger; 049 050 import org.apache.tools.ant.Project; 051 import org.apache.tools.ant.BuildException; 052 import org.apache.tools.ant.BuildListener; 053 import org.apache.tools.ant.BuildEvent; 054 import org.apache.tools.ant.types.Path; 055 056 /** 057 * Default implementation of a project context. 058 * 059 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 060 * @version 1.1.1 061 */ 062 public final class DefaultContext implements Context 063 { 064 private static final Layout CLASSIC_LAYOUT = new ClassicLayout(); 065 066 private final Project m_project; 067 private final Resource m_resource; 068 069 private Path m_runtime; 070 private Path m_test; 071 072 /** 073 * Creation of a new project build context. 074 * @param project the unconfigured Ant project 075 * @exception Exception if an error occurs during context extablishment 076 */ 077 public DefaultContext( Project project ) throws Exception 078 { 079 this( 080 newResource( project.getBaseDir() ), 081 project ); 082 } 083 084 /** 085 * Creation of a new project build context. 086 * @param resource the resource definition 087 * @param project the Ant project 088 * @exception Exception if an error occurs during context extablishment 089 */ 090 public DefaultContext( Resource resource, Project project ) throws Exception 091 { 092 m_project = project; 093 m_resource = resource; 094 095 Library library = resource.getLibrary(); 096 project.addReference( "project.timestamp", new Date() ); 097 098 File basedir = resource.getBaseDir(); 099 if( !basedir.equals( project.getBaseDir() ) ) 100 { 101 project.setBaseDir( resource.getBaseDir() ); 102 } 103 104 project.addReference( "project.context", this ); 105 106 String[] names = resource.getPropertyNames(); 107 for( int i=0; i<names.length; i++ ) 108 { 109 String name = names[i]; 110 String value = resource.getProperty( name ); 111 setProperty( name, value ); 112 } 113 114 setProperty( "project.name", resource.getName() ); 115 setProperty( "project.version", resource.getVersion() ); 116 setProperty( "project.resource.path", resource.getResourcePath() ); 117 setProperty( "project.basedir", resource.getBaseDir().toString() ); 118 119 File cache = Transit.getInstance().getCacheDirectory(); 120 String cachePath = cache.getCanonicalPath(); 121 setProperty( "project.cache", cachePath ); 122 123 Filter[] filters = resource.getFilters(); 124 for( int i=0; i<filters.length; i++ ) 125 { 126 Filter filter = filters[i]; 127 String token = filter.getToken(); 128 try 129 { 130 String value = filter.getValue( resource ); 131 String resolved = resource.resolve( value ); 132 project.getGlobalFilterSet().addFilter( token, resolved ); 133 } 134 catch( Exception e ) 135 { 136 final String error = 137 "Error while attempting to setup the filter [" + token + "]."; 138 throw new BuildException( error, e ); 139 } 140 } 141 142 setProperty( "project.nl", "\n" ); 143 setProperty( 144 "project.line", 145 "---------------------------------------------------------------------------\n", false ); 146 setProperty( 147 "project.info", 148 "---------------------------------------------------------------------------\n" 149 + resource.getResourcePath() 150 + "#" 151 + resource.getVersion() 152 + "\n---------------------------------------------------------------------------", false ); 153 154 setProperty( "project.src.dir", getSrcDirectory().toString() ); 155 setProperty( "project.src.main.dir", getSrcMainDirectory().toString() ); 156 setProperty( "project.src.test.dir", getSrcTestDirectory().toString() ); 157 setProperty( "project.etc.dir", getEtcDirectory().toString() ); 158 setProperty( "project.etc.main.dir", getEtcMainDirectory().toString() ); 159 setProperty( "project.etc.test.dir", getEtcTestDirectory().toString() ); 160 setProperty( "project.etc.data.dir", getEtcDataDirectory().toString() ); 161 162 setProperty( "project.target.dir", getTargetDirectory().toString() ); 163 setProperty( "project.target.build.main.dir", getTargetBuildMainDirectory().toString() ); 164 setProperty( "project.target.build.test.dir", getTargetBuildTestDirectory().toString() ); 165 setProperty( "project.target.classes.main.dir", getTargetClassesMainDirectory().toString() ); 166 setProperty( "project.target.classes.test.dir", getTargetClassesTestDirectory().toString() ); 167 setProperty( "project.target.deliverables.dir", getTargetDeliverablesDirectory().toString() ); 168 setProperty( "project.target.test.dir", getTargetTestDirectory().toString() ); 169 setProperty( "project.target.reports.dir", getTargetReportsDirectory().toString() ); 170 171 // add properties dealing with produced types 172 173 addProductionProperties(); 174 175 // add listeners declared in the builder configuration 176 177 ListenerDirective[] listeners = StandardBuilder.CONFIGURATION.getListenerDirectives(); 178 for( int i=0; i<listeners.length; i++ ) 179 { 180 ListenerDirective directive = listeners[i]; 181 project.log( "adding listener: " + directive.getName(), Project.MSG_VERBOSE ); 182 BuildListener buildListener = loadBuildListener( directive ); 183 project.addBuildListener( buildListener ); 184 BuildEvent event = new BuildEvent( project ); 185 buildListener.buildStarted( event ); 186 } 187 } 188 189 private void addProductionProperties() throws IOException 190 { 191 Resource resource = getResource(); 192 Type[] types = resource.getTypes(); 193 for( int i=0; i<types.length; i++ ) 194 { 195 Type type = types[i]; 196 String id = type.getID(); 197 File file = getTargetDeliverable( id ); 198 String path = file.getCanonicalPath(); 199 setProperty( "project.deliverable." + id + ".path", path ); 200 File dir = file.getParentFile(); 201 String spec = dir.getCanonicalPath(); 202 setProperty( "project.deliverable." + id + ".dir", spec ); 203 String base = getLayoutBase( id ); 204 setProperty( "project.cache." + id + ".dir", base ); 205 String address = getLayoutPath( id ); 206 setProperty( "project.cache." + id + ".path", address ); 207 } 208 } 209 210 private void setProperty( String key, String value ) 211 { 212 setProperty( key, value, true ); 213 } 214 215 private void setProperty( String key, String value, boolean verbose ) 216 { 217 Project project = getProject(); 218 String v = project.getProperty( key ); 219 if( null == v ) 220 { 221 if( verbose ) 222 { 223 project.log( "setting property [" + key + "] to [" + value + "]", Project.MSG_VERBOSE ); 224 } 225 project.setProperty( key, value ); 226 } 227 else if( !value.equals( v ) ) 228 { 229 if( verbose ) 230 { 231 project.log( "updating property [" + key + "] to [" + value + "]", Project.MSG_VERBOSE ); 232 } 233 project.setProperty( key, value ); 234 } 235 } 236 237 /** 238 * Initialize the context during which runtime and test path objects are 239 * established as project references. 240 */ 241 public void init() 242 { 243 if( m_runtime != null ) 244 { 245 return; 246 } 247 248 final Path compileSrcPath = new Path( m_project ); 249 File srcMain = getTargetBuildMainDirectory(); 250 compileSrcPath.createPathElement().setLocation( srcMain ); 251 m_project.addReference( "project.build.src.path", compileSrcPath ); 252 m_runtime = createPath( Scope.RUNTIME ); 253 m_project.addReference( "project.compile.path", m_runtime ); 254 m_test = createPath( Scope.TEST ); 255 if( m_resource.isa( "jar" ) ) 256 { 257 File deliverables = getTargetDeliverablesDirectory(); 258 File jars = new File( deliverables, "jars" ); 259 String filename = getLayoutFilename( "jar" ); 260 File jar = new File( jars, filename ); 261 m_test.createPathElement().setLocation( jar ); 262 } 263 final File testClasses = getTargetClassesTestDirectory(); 264 m_test.createPathElement().setLocation( testClasses ); 265 m_project.addReference( "project.test.path", m_test ); 266 } 267 268 /** 269 * Return the associated project. 270 * @return the ant project 271 */ 272 public Project getProject() 273 { 274 return m_project; 275 } 276 277 /** 278 * Return the value of a property. 279 * @param key the property key 280 * @return the property value or null if undefined 281 */ 282 public String getProperty( String key ) 283 { 284 return getProperty( key, null ); 285 } 286 287 /** 288 * Return the value of a property. If the project contains a declaration 289 * for the property then that value will be returned, otherwise the property 290 * will be resolved relative to the current resource. 291 * 292 * @param key the property key 293 * @param value the default value 294 * @return the property value or null if undefined 295 */ 296 public String getProperty( String key, String value ) 297 { 298 String result = m_project.getProperty( key ); 299 if( null != result ) 300 { 301 return result; 302 } 303 else 304 { 305 return getResource().getProperty( key, value ); 306 } 307 } 308 309 /** 310 * Return an Ant path suitable for comile or runtime usage. If the supplied scope is 311 * less than Scope.RUNTIME a runtime path is returned otherwise the test path is 312 * returned. 313 * @param scope the build scope 314 * @return the path object 315 */ 316 public Path getPath( Scope scope ) 317 { 318 if( m_runtime == null ) 319 { 320 init(); 321 } 322 if( scope.isLessThan( Scope.TEST ) ) 323 { 324 return m_runtime; 325 } 326 else 327 { 328 return m_test; 329 } 330 } 331 332 /** 333 * Return the active resource. 334 * @return the resource definition 335 */ 336 public Resource getResource() 337 { 338 return m_resource; 339 } 340 341 /** 342 * Return the resource library. 343 * @return the library 344 */ 345 public Library getLibrary() 346 { 347 return m_resource.getLibrary(); 348 } 349 350 /** 351 * Return the project source directory. 352 * @return the directory 353 */ 354 public File getSrcDirectory() 355 { 356 return createFile( "src" ); 357 } 358 359 /** 360 * Return the project source main directory. 361 * @return the directory 362 */ 363 public File getSrcMainDirectory() 364 { 365 String path = getProperty( "project.src.main", "src/main" ); 366 return createFile( path ); 367 } 368 369 /** 370 * Return the project source test directory. 371 * @return the directory 372 */ 373 public File getSrcTestDirectory() 374 { 375 String path = getProperty( "project.src.test", "src/test" ); 376 return createFile( path ); 377 } 378 379 /** 380 * Return the project source docs directory. 381 * @return the directory 382 */ 383 public File getSrcDocsDirectory() 384 { 385 String path = getProperty( "project.src.docs", "src/docs" ); 386 return createFile( path ); 387 } 388 389 /** 390 * Return the project etc directory. 391 * @return the directory 392 */ 393 public File getEtcDirectory() 394 { 395 String path = getProperty( "project.etc", "etc" ); 396 return createFile( path ); 397 } 398 399 /** 400 * Return the project etc/main directory. 401 * @return the directory 402 */ 403 public File getEtcMainDirectory() 404 { 405 String path = getProperty( "project.etc.main", "etc/main" ); 406 return createFile( path ); 407 } 408 409 /** 410 * Return the project etc/test directory. 411 * @return the directory 412 */ 413 public File getEtcTestDirectory() 414 { 415 String path = getProperty( "project.etc.test", "etc/test" ); 416 return createFile( path ); 417 } 418 419 /** 420 * Return the project etc/resources directory. 421 * @return the directory 422 */ 423 public File getEtcDataDirectory() 424 { 425 String path = getProperty( "project.etc.data", "etc/data" ); 426 return createFile( path ); 427 } 428 429 /** 430 * Return the project target directory. 431 * @return the directory 432 */ 433 public File getTargetDirectory() 434 { 435 return createFile( "target" ); 436 } 437 438 /** 439 * Return a directory within the target directory. 440 * @param path the path 441 * @return the directory 442 */ 443 public File getTargetDirectory( String path ) 444 { 445 return new File( getTargetDirectory(), path ); 446 } 447 448 /** 449 * Return the project target temp directory. 450 * @return the directory 451 */ 452 public File getTargetTempDirectory() 453 { 454 return new File( getTargetDirectory(), "temp" ); 455 } 456 457 /** 458 * Return the project target build directory. 459 * @return the directory 460 */ 461 public File getTargetBuildDirectory() 462 { 463 return new File( getTargetDirectory(), "build" ); 464 } 465 466 /** 467 * Return the project target build main directory. 468 * @return the directory 469 */ 470 public File getTargetBuildMainDirectory() 471 { 472 return new File( getTargetBuildDirectory(), "main" ); 473 } 474 475 /** 476 * Return the project target build test directory. 477 * @return the directory 478 */ 479 public File getTargetBuildTestDirectory() 480 { 481 return new File( getTargetBuildDirectory(), "test" ); 482 } 483 484 /** 485 * Return the project target build docs directory. 486 * @return the directory 487 */ 488 public File getTargetBuildDocsDirectory() 489 { 490 return new File( getTargetBuildDirectory(), "docs" ); 491 } 492 493 /** 494 * Return the project target root classes directory. 495 * @return the directory 496 */ 497 public File getTargetClassesDirectory() 498 { 499 return new File( getTargetDirectory(), "classes" ); 500 } 501 502 /** 503 * Return the project target main classes directory. 504 * @return the directory 505 */ 506 public File getTargetClassesMainDirectory() 507 { 508 return new File( getTargetClassesDirectory(), "main" ); 509 } 510 511 /** 512 * Return the project target test classes directory. 513 * @return the directory 514 */ 515 public File getTargetClassesTestDirectory() 516 { 517 return new File( getTargetClassesDirectory(), "test" ); 518 } 519 520 /** 521 * Return the project target reports directory. 522 * @return the directory 523 */ 524 public File getTargetReportsDirectory() 525 { 526 return new File( getTargetDirectory(), "reports" ); 527 } 528 529 /** 530 * Return the project target test reports directory. 531 * @return the directory 532 */ 533 public File getTargetReportsTestDirectory() 534 { 535 return new File( getTargetReportsDirectory(), "test" ); 536 } 537 538 /** 539 * Return the project target main reports directory. 540 * @return the directory 541 */ 542 public File getTargetReportsMainDirectory() 543 { 544 return new File( getTargetReportsDirectory(), "main" ); 545 } 546 547 /** 548 * Return the project target javadoc reports directory. 549 * @return the directory 550 */ 551 public File getTargetReportsJavadocDirectory() 552 { 553 return new File( getTargetReportsDirectory(), "api" ); 554 } 555 556 /** 557 * Return the project target reports docs directory. 558 * @return the directory 559 */ 560 public File getTargetDocsDirectory() 561 { 562 return new File( getTargetDirectory(), "docs" ); 563 } 564 565 /** 566 * Return the project target test directory. 567 * @return the directory 568 */ 569 public File getTargetTestDirectory() 570 { 571 return new File( getTargetDirectory(), "test" ); 572 } 573 574 /** 575 * Return the project target deliverables directory. 576 * @return the directory 577 */ 578 public File getTargetDeliverablesDirectory() 579 { 580 return new File( getTargetDirectory(), "deliverables" ); 581 } 582 583 /** 584 * Return the project target deliverables directory. 585 * @param type the deliverable type 586 * @return the directory 587 */ 588 public File getTargetDeliverable( String type ) 589 { 590 Artifact artifact = m_resource.getArtifact( type ); 591 String path = CLASSIC_LAYOUT.resolveFilename( artifact ); 592 String types = type + "s"; 593 File root = new File( getTargetDeliverablesDirectory(), types ); 594 return new File( root, path ); 595 } 596 597 /** 598 * Create a file relative to the resource basedir. 599 * @param path the relative path 600 * @return the directory 601 */ 602 public File createFile( String path ) 603 { 604 File basedir = m_resource.getBaseDir(); 605 return new File( basedir, path ); 606 } 607 608 /** 609 * Return a filename using the layout strategy employed by the cache. 610 * @param id the artifact type 611 * @return the filename 612 */ 613 public String getLayoutFilename( String id ) 614 { 615 Artifact artifact = m_resource.getArtifact( id ); 616 return Transit.getInstance().getCacheLayout().resolveFilename( artifact ); 617 } 618 619 /** 620 * Return the directory path representing the module structure and type 621 * using the layout strategy employed by the cache. 622 * @param id the artifact type 623 * @return the path from the root of the cache to the directory containing the artifact 624 */ 625 public String getLayoutBase( String id ) 626 { 627 Artifact artifact = m_resource.getArtifact( id ); 628 return Transit.getInstance().getCacheLayout().resolveBase( artifact ); 629 } 630 631 /** 632 * Return the full path to an artifact using the layout employed by the cache. 633 * @param id the artifact type 634 * @return the full path including base path and filename 635 */ 636 public String getLayoutPath( String id ) 637 { 638 Artifact artifact = m_resource.getArtifact( id ); 639 return Transit.getInstance().getCacheLayout().resolvePath( artifact ); 640 } 641 642 /** 643 * Utility operation to construct a new classpath path instance. 644 * @param scope the build scope 645 * @return the path 646 */ 647 public Path createPath( Scope scope ) 648 { 649 try 650 { 651 Resource[] resources = m_resource.getClasspathProviders( scope ); 652 return createPath( resources, true, true ); 653 } 654 catch( Exception e ) 655 { 656 final String error = 657 "Unexpected error while constructing path instance for the scope: " + scope; 658 throw new RuntimeException( error, e ); 659 } 660 } 661 662 /** 663 * Utility operation to construct a new path using a supplied array of resources. 664 * @param resources the resource to use in path construction 665 * @return the path 666 */ 667 public Path createPath( Resource[] resources ) 668 { 669 return createPath( resources, true, false ); 670 } 671 672 /** 673 * Utility operation to construct a new path using a supplied array of resources. 674 * @param resources the resources to use in path construction 675 * @param resolve if true force local caching of the artifact 676 * @param filter if true restrict path entries to resources that produce jars 677 * @return the path 678 */ 679 public Path createPath( Resource[] resources, boolean resolve, boolean filter ) 680 { 681 final Path path = new Path( m_project ); 682 File cache = (File) m_project.getReference( "dpml.cache" ); 683 for( int i=0; i<resources.length; i++ ) 684 { 685 Resource resource = resources[i]; 686 if( !resource.equals( getResource() ) ) 687 { 688 if( filter && resource.isa( "jar" ) ) 689 { 690 Artifact artifact = resource.getArtifact( "jar" ); 691 addToPath( cache, path, artifact, resolve ); 692 } 693 else 694 { 695 Type[] types = resource.getTypes(); 696 for( int j=0; j<types.length; j++ ) 697 { 698 Artifact artifact = resource.getArtifact( types[j].getID() ); 699 addToPath( cache, path, artifact, resolve ); 700 } 701 } 702 } 703 } 704 return path; 705 } 706 707 private void addToPath( File cache, Path path, Artifact artifact, boolean resolve ) 708 { 709 Artifact target = getTargetArtifact( artifact ); 710 String location = Transit.getInstance().getCacheLayout().resolvePath( target ); 711 File file = new File( cache, location ); 712 path.createPathElement().setLocation( file ); 713 714 if( resolve ) 715 { 716 resolveArtifact( artifact ); 717 } 718 } 719 720 private Artifact getTargetArtifact( Artifact artifact ) 721 { 722 String scheme = artifact.getScheme(); 723 if( !Artifact.LINK.equals( scheme ) ) 724 { 725 return artifact; 726 } 727 else 728 { 729 try 730 { 731 LinkManager manager = Transit.getInstance().getLinkManager(); 732 URI uri = manager.getTargetURI( artifact.toURI() ); 733 return Artifact.createArtifact( uri ); 734 } 735 catch( IOException e ) 736 { 737 final String error = 738 "Unable to resolve link artifact [" 739 + artifact 740 + "]."; 741 throw new BuildException( error, e ); 742 } 743 } 744 } 745 746 private void resolveArtifact( Artifact artifact ) 747 { 748 try 749 { 750 URL url = artifact.toURL(); 751 url.openStream(); 752 } 753 catch( IOException e ) 754 { 755 final String error = 756 "Unable to resolve artifact [" 757 + artifact 758 + "]."; 759 throw new BuildException( error, e ); 760 } 761 } 762 763 private static Resource newResource( File basedir ) throws Exception 764 { 765 Logger logger = new DefaultLogger(); 766 DefaultLibrary library = new DefaultLibrary( logger ); 767 return library.locate( basedir.getCanonicalFile() ); 768 } 769 770 private static String flatternDependencies( String[] deps ) 771 { 772 if( deps.length == 0 ) 773 { 774 return null; 775 } 776 StringBuffer buffer = new StringBuffer(); 777 for( int i=0; i<deps.length; i++ ) 778 { 779 if( i>0 ) 780 { 781 buffer.append( "," ); 782 } 783 String dep = deps[i]; 784 buffer.append( dep ); 785 } 786 return buffer.toString(); 787 } 788 789 private BuildListener loadBuildListener( ListenerDirective listener ) 790 { 791 String name = listener.getName(); 792 URI uri = listener.getURI(); 793 try 794 { 795 String classname = listener.getClassname(); 796 Object object = loadInstance( name, uri, classname ); 797 return (BuildListener) object; 798 } 799 catch( ClassCastException e ) 800 { 801 final String error = 802 "Build listener [" 803 + name 804 + "] from uri [" 805 + uri 806 + "] does not implement " 807 + BuildListener.class.getName(); 808 throw new BuilderError( error ); 809 } 810 } 811 812 private Object loadInstance( String name, URI uri, String classname ) 813 { 814 if( null == uri ) 815 { 816 try 817 { 818 ClassLoader classloader = getClass().getClassLoader(); 819 Class clazz = classloader.loadClass( classname ); 820 Object[] args = new Object[]{this}; 821 return Plugin.instantiate( clazz, args ); 822 } 823 catch( Throwable e ) 824 { 825 final String error = 826 "Internal error while attempting to load a local plugin." 827 + "\nClass: " + classname 828 + "\nName: " + name; 829 throw new BuilderError( error, e ); 830 } 831 } 832 else 833 { 834 ClassLoader context = Thread.currentThread().getContextClassLoader(); 835 try 836 { 837 ClassLoader classloader = getClass().getClassLoader(); 838 Thread.currentThread().setContextClassLoader( classloader ); 839 Object[] params = new Object[]{this}; 840 Part part = Part.load( uri ); 841 if( null == classname ) 842 { 843 return part.instantiate( params ); 844 } 845 else 846 { 847 ClassLoader loader = part.getClassLoader(); 848 Class c = loader.loadClass( classname ); 849 return Plugin.instantiate( c, params ); 850 } 851 } 852 catch( Throwable e ) 853 { 854 final String error = 855 "Internal error while attempting to load plugin." 856 + "\nURI: " + uri 857 + "\nName: " + name; 858 throw new BuilderError( error, e ); 859 } 860 finally 861 { 862 Thread.currentThread().setContextClassLoader( context ); 863 } 864 } 865 } 866 } 867